﻿// Copyright (c) Argo Zhang (argo@live.ca). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://github.com/LongbowExtensions/

using System.Reflection;

namespace Longbow.Sockets.DataConverters;

/// <summary>
/// Provides a base class for converting socket data into a specified entity type.
/// </summary>
/// <typeparam name="TEntity">The type of entity to convert the socket data into.</typeparam>
public class DataConverter<TEntity> : IDataConverter<TEntity>
{
    /// <summary>
    /// <inheritdoc/>
    /// </summary>
    /// <param name="data"></param>
    /// <param name="entity"></param>
    /// <returns></returns>
    public virtual bool TryConvertTo(ReadOnlyMemory<byte> data, [NotNullWhen(true)] out TEntity? entity)
    {
        var ret = false;
        entity = default;
        try
        {
            var v = CreateEntity();
            if (Parse(data, v))
            {
                entity = v;
                ret = true;
            }
        }
        catch { }
        return ret;
    }

    /// <summary>
    /// 创建实体实例方法
    /// </summary>
    /// <returns></returns>
    protected virtual TEntity CreateEntity() => Activator.CreateInstance<TEntity>();

    /// <summary>
    /// 将字节数据转换为指定实体类型的实例。
    /// </summary>
    /// <param name="data"></param>
    /// <param name="entity"></param>
    protected virtual bool Parse(ReadOnlyMemory<byte> data, TEntity entity)
    {
        // 使用 SocketDataPropertyAttribute 特性获取数据转换规则
        var ret = false;
        if (entity != null)
        {
            // 通过 SocketDataPropertyConverterAttribute 特性获取属性转换器
            var properties = entity.GetType().GetProperties().Where(p => p.CanWrite).ToList();
            foreach (var p in properties)
            {
                var attr = p.GetCustomAttribute<DataPropertyConverterAttribute>(false);
                if (attr != null)
                {
                    var value = attr.ConvertTo(p.PropertyType, data);
                    var valueType = value?.GetType();
                    if (value == null && IsNullable(p.PropertyType))
                    {
                        p.SetValue(entity, null);
                    }
                    else if (p.PropertyType.IsAssignableFrom(valueType))
                    {
                        p.SetValue(entity, value);
                    }
                }
            }
            ret = true;
        }
        return ret;
    }

    private static bool IsNullable(Type type) => !type.IsValueType || Nullable.GetUnderlyingType(type) != null;
}
